-- PropM
-- Create by weism
-- 附加属性功能模块

module("PropM", package.seeall);

-- 所有的附加属性信息
local props = {};
local allProps = {};
local triggerProps = {};

local ruleToId = {};

-- 所有的规则处理子模块
local rules = {};

-- 内部函数声明
local refreshMonster;
local refreshUser;
local refreshPet;
local refreshBine;
local refreshSummon;
local refreshShip;
local refreshBishop;

-- 刷新属性回调
local refreshCallback = {};

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    allProps = {};

    -- 转换下prop信息表
    local prop_CSV = get_prop_CSV();
    for i = 1, #prop_CSV do
        props[prop_CSV[i].id] = prop_CSV[i];
        props[prop_CSV[i].id].rule = string.trim(prop_CSV[i].rule);

        -- rule到id的映射
        ruleToId[props[prop_CSV[i].id].rule] = prop_CSV[i].id;

        if sizeof(prop_CSV[i].trigger) > 0 then
            local trigger = prop_CSV[i].trigger;

            triggerProps[trigger] = triggerProps[trigger] or {};
            table.insert(triggerProps[trigger], prop_CSV[i].id);
        end
    end

    allProps = table.keys(props);
    table.sort(allProps);

    -- 排序一把，确保和服务器一致
    for _, arr in pairs(triggerProps) do
        table.sort(arr);
    end

    prop_CSV = {};
end


-- 模块初始化
function init()
    loadCsv();

    -- 载入所有的规则处理子模块
    rules = LOAD_PATH2("game/logic/module/prop");

    EventMgr.removeAll("PropM");

    -- 注册子模块的事件回调函数
    registerEventCallbackForRules();
end

-- 注册子模块的事件回调函数
function registerEventCallbackForRules()
    -- 关注死亡事件
    EventMgr.removeAll("PropM/timid_hp");
    EventMgr.register("PropM/timid_hp", event.MONSTER_DIE_LOGIC, function(para)
        local monster = para.monster;
        if not monster then
            return;
        end

        local pos;
        local addHp = 0;

        for pos = 1, GRID_SIZE do
            local grid = DungeonM.getGridByPos(pos);
            local ob = grid.monster;

            -- 开的格子，是怪物，不是原来那头，没挂
            if grid:isOpened() and grid:isMonster() and ob and
                monster ~= ob and not ob:isDead() then
                local addHp = 0;
                local props = PropM.fetchProps(ob, "timid_hp");
                if #props > 0 then
                    -- 加成值
                    local hp = ob:getMaxHp();
                    for _, prop in ipairs(props) do
                        addHp = addHp + math.modf(hp * prop[3] / 1000);
                    end

                    addHp = math.min(addHp, ob:getHp() - 1);
                    if (addHp ~= 0) then
                        local oldAddon = ob.dbase:query("addon_hp", 0);
                        ob.dbase:set("addon_hp", oldAddon - addHp);
                        EventMgr.fire(event.MONSTER_INFO_UPDATED, {["pos"] = pos, });
                    end
                end
            end
        end
    end);

    -- 关注死亡事件
    EventMgr.removeAll("PropM/resonance_hp");
    EventMgr.register("PropM/resonance_hp", event.MONSTER_DIE_LOGIC, function(para)
        local monster = para.monster;
        if not monster then
            return;
        end

        local props = PropM.fetchProps(monster, "resonance_hp");
        if #props <= 0 then
            return;
        end

        local layer = DungeonM.currentLayer();
        -- 先计算出总共加成
        local addHp = 0;
        for _, prop in ipairs(props) do
            addHp = math.modf(addHp + tonumber(prop[2]) + layer / tonumber(prop[3]));
        end

        for pos = 1, GRID_SIZE do
            repeat
                local grid = DungeonM.getGridByPos(pos);
                local ob = grid.monster;

                -- 如果不是怪物，或者不是同类，或者已经死亡了
                if  not grid:isMonster() or not ob or
                    monster:query("base_class_id") ~= ob:query("base_class_id") or
                    monster == ob or ob:isDead() then
                    break;
                end

                local value = math.min(addHp, ob:getHp() - 1);
                local oldAddon = ob.dbase:query("addon_hp", 0);
                ob.dbase:set("addon_hp", oldAddon - value);
                EventMgr.fire(event.MONSTER_INFO_UPDATED, {["pos"] = pos, })
            until true;
        end
    end);

    -- 关注使用卷轴事件
    EventMgr.removeAll("PropM/spell_get_cure");
    EventMgr.register("PropM/spell_get_cure", event.USE_SPELL, function(para)
        local props = PropM.fetchProps(ME.user, "spell_get_cure");
        for _, prop in ipairs(props) do
            PropM.trigger(ME.user, prop[1], prop[2]);
        end
    end);
end

-- 获取子规则
function queryRule(rule)
    if rules[rule] == "TBL" then
        rules[rule] = LOAD_RUN("game/logic/module/prop/" .. rule);
    end

    return rules[rule];
end

-- 属性描述
function getPropDesc(prop)
    local row = getRow(prop[1]);
    if row == nil then
        return "";
    end

    local rule = queryRule(row.rule);

    if not rule then
        cclog("没有相应的子模块: " .. row.id .. ", rule: " .. row.rule);
        return "";
    end

    -- 子规则处理
    if rule.desc then
        return rule.desc(prop)
    end

    local desc = row.desc;
    local attrib = FieldsM.getFieldName(prop[2]);
    if attrib then
        desc = string.gsub(desc, "{0}", attrib)
    elseif row.rule == "magic_effect" or row.rule == "skill_level" or
           row.rule == "attack_skill" then
        -- 魔法名称
        desc = string.gsub(desc, "{0}", SkillM.query(prop[2], "name"));
    elseif row.rule == "gift" or row.rule == "magic_effect_style" or
           row.rule == "magic_unlimited" then
        -- 魔法系别名称
        desc = string.gsub(desc, "{0}", getNameByAttr(prop[2]));
    elseif row.rule == "skill_type_cost" then
        -- 职业系别名称
        desc = string.gsub(desc, "{0}", getNameByDep(prop[2]));
    elseif row.rule == "magic_last" then
        -- 魔法名称
        if SkillM.query(prop[2], "name") == nil then
            desc = string.gsub(desc, "{0}", getNameByTypes(prop[2]));
        else
            desc = string.gsub(desc, "{0}", SkillM.query(prop[2], "name"));
        end
    end

    if row.rule == "attrib_by_floor" then
        -- 根据楼层增加属性
        local v = math.floor(prop[3] * DungeonM.currentLayer() / 10);
        if v ~= 0 then
            desc = string.gsub(desc, "{1}", math.floor(prop[3] * DungeonM.currentLayer() / 10));
        else
            desc = "";
        end
    end

    if row.rule == "slime_attack" or row.rule == "slime_magic" or
       row.rule == "slime_attack2" or row.rule == "slime_magic2" then
        -- {0}替换成冈布奥名称
        desc = string.gsub(desc, "{0}", PetM.query(prop[2], "name"));
    end

    local v = prop[3];
    if row["type"] == PROP_TYPE_RATIO then
        v = math.modf(v / 10);
    end

    -- 如果是闪避或命中属性，替换数值时后面加%
    if "dodge" == prop[2] or "accuracy" == prop[2] then
        desc = string.gsub(desc, "{1}", v .. "%%");
    else
        desc = string.gsub(desc, "{1}", v);
    end

    -- 增加或减少这种,或是按数值变更符号的
    if prop[3] >= 0 then
        desc = string.gsub(desc, "{sign}", getLocStr("reduce"));
        desc = string.gsub(desc, "{sign2}", "+");
    else
        desc = string.gsub(desc, "{sign}", getLocStr("addition"));
        desc = string.gsub(desc, "-", "", 1);
        desc = string.gsub(desc, "{sign2}", "-");
    end

    -- 最后处理一把，把描述中的~替换为 ～
    desc = string.gsub(desc, "~", getLocStr("hyphen"));

    return desc;
end

-- 属性描述 用?代替
function getPropDescPro(prop,replace)
    local row = getRow(prop[1]);
    if row == nil then
        return "";
    end

    local rule = queryRule(row.rule);

    if not rule then
        cclog("没有相应的子模块: " .. row.id .. ", rule: " .. row.rule);
        return "";
    end

    -- 子规则处理
    if rule.desc then
        return rule.desc(prop)
    end

    local desc = row.desc;
    local attrib = FieldsM.getFieldName(prop[2]);
    if attrib then
        desc = string.gsub(desc, "{0}", attrib)
    elseif row.rule == "magic_effect" or row.rule == "skill_level" then
        -- 技能效果加
        desc = string.gsub(desc, "{0}", SkillM.query(prop[2], "name"));
    elseif row.rule == "gift" or row.rule == "magic_effect_style" or
           row.rule == "magic_unlimited" then
        desc = string.gsub(desc, "{0}", getNameByAttr(prop[2]));
    elseif row.rule == "magic_last" then
        -- 魔法名称
        if SkillM.query(prop[2], "name") == nil then
            desc = string.gsub(desc, "{0}", getNameByAttr(prop[2]));
        else
            desc = string.gsub(desc, "{0}", SkillM.query(prop[2], "name"));
        end
    end


    local v = prop[3];
    if row["type"] == PROP_TYPE_RATIO then
        v = math.modf(v / 10);
    end
    if replace then
        desc = string.gsub(desc, "{1}", replace);
    else
        desc = string.gsub(desc, "{1}", v);
    end


    -- 增加或减少这种
    if prop[3] >= 0 then
        desc = string.gsub(desc, "{sign}", getLocStr("reduce"));
    else
        desc = string.gsub(desc, "{sign}", getLocStr("addition"));
    end

    return desc
end

function query(propId, path)
    local m = props[propId];
    if nil == m then
        return nil;
    end
    if (path == "" or path == nil) then
        return m;
    else
        return m[path];
    end
end

-- 获取id
function getPropId(rule)
    return ruleToId[rule];
end

-- 获取属性别名
function getPropAlias(propId, prop)
    local row = getRow(propId);
    local rule = queryRule(row.rule);

    if prop and rule and rule.title then
        -- 子规则处理
        return rule.title(prop);
    end

    -- 默认
    return query(propId, "alias") or "";
end

-- 应用所有类型为prop的附加属性，应用于数值v
function apply(prop, v)
    local row = getRow(prop[1]);
    if not row then
        return;
    end

    local rule = queryRule(row.rule);

    if not rule then
        cclog("没有相应的子模块: " .. row.id .. ", rule: " .. row.rule);
    end

    return rule.apply(prop, v);
end

-- 清除了一条属性（这里之后删除后的处理）
function clearProp(target, prop)
    local row = getRow(prop[1]);
    if not row then
        return;
    end

    local rule = queryRule(row.rule);
    if rule and rule.clear then
        -- 子规则处理
        rule.clear(target, prop);
    end

    -- 标记属性全部脏了
    AttribM.setAllDirty(target, true);

    -- 抛出事件，需要先记录了再抛出
    if target.type == OBJECT_TYPE_USER then
        if not ScheduleM.isSchemeAlive("PropM:clearProp") then
            ScheduleM.createScheme("PropM:clearProp", function() EventMgr.fire(event.USER_INFO_UPDATED, "clear_prop"); end, 0.01, false);
        end
    end
end

-- 记录一票属性
function multiRecord(target, props, path)
    assert(type(props[1]) == "table" and #props[1] == 4);

    target.dbase:setTemp("prop", path, props);

    -- 重新整理下属性
    arrangeProps(target);
end

-- 记录一条附加属性
function record(target, prop, path, notArrange)
    local row = PropM.getRow(prop[1]);
    if not row then
        return;
    end
    local rule = queryRule(row.rule);
    if not rule then
        cclog("没有相应的子模块: " .. row.id .. ", rule: " .. row.rule);
        return;
    end

    if (rule.record(target, prop, path)) then

        if not notArrange then
            -- 合并属性
            appendProp(target, prop);
        end

        -- 抛出事件，需要先记录了再抛出
        if target.type == OBJECT_TYPE_USER then
            if not ScheduleM.isSchemeAlive("PropM:apply_prop") then
                ScheduleM.createScheme("PropM:apply_prop", function() EventMgr.fire(event.USER_INFO_UPDATED, "apply_prop"); end, 0.01, false);
            end

            recordForever(target, prop, path);
        end

        return;
    end

    -- 走默认的存储流程
    local v = target.dbase:queryTemp("prop", path, {});
    table.insert(v, prop);
    target.dbase:setTemp("prop", path, v);

    if not notArrange then
        -- 合并属性
        appendProp(target, prop);
    end

    -- 抛出事件，需要先记录了再抛出
    if target.type == OBJECT_TYPE_USER then
        if not ScheduleM.isSchemeAlive("PropM:apply_prop") then
            ScheduleM.createScheme("PropM:apply_prop", function() EventMgr.fire(event.USER_INFO_UPDATED, "apply_prop"); end, 0.01, false);
        end

        recordForever(target, prop, path);
    end
end

-- 删除永久属性记录
function deleteForever(who, path)
    if who.type ~= OBJECT_TYPE_USER then
        return;
    end

    if not FormulaM.invoke("IS_FOREVER_PROP", path) then
        return;
    end

    local m = who.dbase:query("forever_prop", {});
    if not m[path] then
        return;
    end

    -- 置为空table就行了，下次restore时会把无效的属性清除
    m[path] = {};
end

-- 记录下持续整个迷宫的属性（下次登录需要还原）
function recordForever(who, prop, path)
    if not FormulaM.invoke("IS_FOREVER_PROP", path) then
        return;
    end

    local m = who.dbase:query("forever_prop", {});
    m[path] = m[path] or {};

    table.insert(m[path], prop);
    who.dbase:set("forever_prop", m);
end

-- 还原永久（持续整个迷宫）属性
function restoreForeverProp(who, notArrange)
    if who.type ~= OBJECT_TYPE_USER then
        return;
    end

    local m = who.dbase:query("forever_prop", {});
    who.dbase:delete("forever_prop");

    for path, props in pairs(m) do
        -- 旧属性先删除
        who.dbase:deleteTemp("prop", path);

        if props.array then
            props = props:toTable();
        end
        for _, prop in pairs(props) do
            if prop.array then
                prop = prop:toTable();
            end

            record(who, prop, path, true);
        end
    end

    if not notArrange then
        -- 整理下属性
        arrangeProps(who);
    end
end

-- 查找prop信息
function getRow(id)
    return props[id];
end

-- 获取所有属性id
function getAllProps()
    return allProps;
end

-- 获取需要触发的属性
function getTriggerProps(trigger)
    trigger = trigger or "round" -- 默认取回合触发的
    return triggerProps[trigger];
end

-- 注册刷新属性时的回调
function registerRefreshCallback(key, func)
    refreshCallback[key] = func;
end

-- 刷新对象的附加属性
function refresh(target)
    -- 这里没有必要先删除prop，不过需要保证刷新属性的顺序（比如equip对skill有影响，那么需要先刷新equip再刷新skill）
    --target.dbase:setTemp("prop", {});

    -- 先删除，再附加
    target.dbase:deleteTemp("combine_props");
    target.dbase:deleteTemp("sorted_props");

    if target.type == OBJECT_TYPE_MONSTER then
        -- 刷新怪物的附加属性
        refreshMonster(target);
    elseif target.type == OBJECT_TYPE_USER then
        -- 刷新玩家的附加属性
        refreshUser(target);
    elseif target.type == OBJECT_TYPE_PET then
        -- 刷新宠物的属性
        refreshPet(target);
    elseif target.type == OBJECT_TYPE_BINE then
        -- 刷新魔藤
        refreshBine(target);
    elseif target.type == OBJECT_TYPE_SUMMON then
        -- 召唤兽
        refreshSummon(target);
    elseif target.type == OBJECT_TYPE_GHOST_SHIP then
        -- 幽灵船
        refreshShip(target);
    elseif target.type == OBJECT_TYPE_BISHOP then
        -- 主教
        refreshBishop(target);
    elseif target.type == OBJECT_TYPE_AVALON_CANNON then
        -- 阿瓦隆巨炮
        refreshAvalonCannon(target);
    end

    -- 回调
    for _, f in pairs(refreshCallback) do
        f(target);
    end

    -- 还原永久（持续整个迷宫）属性
    restoreForeverProp(target, true);

    -- 整理合并一次属性
    arrangeProps(target);

    -- 抛出事件
    EventMgr.fire(event.RERESH_PROP, {["who"] = target, ["type"] = "all", });
end

-- 获取玩家固定属性
function getFixedProps(propId, key)
    if type(propId) == "string" then
        propId = getPropId(propId);
    end

    local arr = {};
    local props = ME.user.dbase:queryTemp("prop", {});
    for path, _ in pairs(props) do
        if path == "equip" or path == "skill" or path == "lictor" or path == "pet" or FormulaM.invoke("IS_FOREVER_PROP", path) then
            arr = table.append(arr, props[path]);
        end
    end

    local v = 0;
    for _, prop in ipairs(arr) do
        if prop[1] == propId and tostring(prop[2]) == tostring(key) then
            v = v + tonumber(prop[3]);
        end
    end

    return { propId, key, v, -1, };
end

-- 获取所有属性为prop_id的属性
function fetchProps(target, propId)
    if type(propId) == "string" then
        propId = getPropId(propId);
    end

    local sorted_props = target.dbase:queryTemp("sorted_props", {});

    return sorted_props[propId] or {};
end

-- 获取辅助属性（建筑辅助）
function getBuildingAidsProp(target, propId, key)
    if type(propId) == "string" then
        propId = getPropId(propId);
    end

    -- 策划需求，观众也要翻血
    -- if target.isAudience and target:isAudience() then
    --     -- 观众不受影响
    --     return {propId, key, 0, -1};
    -- end

    if not DungeonM.getCurrentDungeon() then
        -- 没有地牢数据
        return {propId, key, 0, -1};
    end

    -- 可以辅助的建筑
    local classes = {GRID_TYPE_MACHINE_PLATFORM, };
    for pos = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(pos);
        if table.indexOf(classes, grid.type) >= 0 and
            grid.state ~= GRID_STATE_DISABLE then
            -- 允许增益的类型/格子未失效
            local skillId = grid.buff_skill;
            if type(skillId) ~= "number" then
                error("作战平台没有配置buff_skill！");
            end
            local props = SkillM.getSkillProps(skillId);
            for _, prop in pairs(props) do
                -- 找到一个便可，只取一个
                if prop[1] == propId and prop[2] == key then
                    return prop;
                end
            end
        end
    end

    return {propId, key, 0, -1};
end

-- 合并某一类型的附加属性，直接相加
function combine(target, propId, srcKey)
    if type(propId) == "string" then
        propId = getPropId(propId);
    end

    local key = tostring(srcKey);
    local arrangeProp = target.dbase:queryTemp("combine_props", {});
    local v = 0;
    local round = -1;
    local prop = arrangeProp[propId] and arrangeProp[propId][key];
    if prop then
        v = prop[3];
        round = prop[4] > 0 and prop[4] or -1;
    end

    return { propId, srcKey, v, round };
end

-- 合并属性
function combineProps(targets, propId, srcKey)
    local round = -1;
    local v = 0;

    local key = tostring(srcKey);
    for _, ob in ipairs(targets) do
        local arrangeProp = ob.dbase:queryTemp("combine_props", {});

        if arrangeProp[propId] and arrangeProp[propId][key] then
            local prop = arrangeProp[propId][key];

            v = v + prop[3];
            if prop[4] > 0 then
                round = prop[4];
            end
        end
    end

    return { propId, srcKey, v, round };
end

-- 整理属性，每次refresh之后
function arrangeProps(who)
    local props = who.dbase:queryTemp("prop", {});
    local arrangeProp = {};
    local tonumber = sys_tonumber;
    local tostring = tostring;
    local dup = table.copy;

    for path, propList in pairs(props) do
        for _, prop in ipairs(propList) do
            local key = tostring(prop[2]);
            local propId = tonumber(prop[1]) or 0;

            arrangeProp[propId] = arrangeProp[propId] or {};

            local arr = arrangeProp[propId][key];

            if not arr then
                arr = dup(prop);
            else
                arr[3] = arr[3] + tonumber(prop[3]) or 0;
            end

            -- 始终取最大那个，确保服务器/客户端一致。-1最大
            if arr[4] == -1 or prop[4] == -1 then
                arr[4] = -1;
            else
                arr[4] = math.max(arr[4], prop[4]);
            end

            arrangeProp[propId][key] = arr;
        end
    end
    -- 缓存起来
    who.dbase:setTemp("combine_props", arrangeProp);

    local sorted_props = {};
    for propId, propMap in pairs(arrangeProp) do
        -- 排序一遍
        local propList = table.values(propMap);
        table.sort(propList, function(a, b)
            local aKey = tostring(a[2]);
            local bKey = tostring(b[2]);
            return aKey > bKey;
        end);

        sorted_props[propId] = propList;
    end
    -- 缓存排好序的属性
    who.dbase:setTemp("sorted_props", sorted_props);
end

-- 合并属性
function appendProp(who, prop)
    local arrangeProp = who.dbase:queryTemp("combine_props", {});
    local propId = tonumber(prop[1]);
    local key = tostring(prop[2]);

    arrangeProp[propId] = arrangeProp[propId] or {};

    local arr = arrangeProp[propId][key];
    if not arr then
        arr = table.copy(prop);
    else
        arr[3] = arr[3] + tonumber(prop[3]);
    end

    arrangeProp[propId][key] = arr;
    who.dbase:setTemp("combine_props", arrangeProp);

    -- 排序一遍
    local propList = table.values(arrangeProp[propId]);
    table.sort(propList, function(a, b)
        local aKey = tostring(a[2]);
        local bKey = tostring(b[2]);
        return aKey > bKey;
    end);
    local sorted_props = who.dbase:queryTemp("sorted_props", {});
    sorted_props[propId] = propList;

     -- 缓存排好序的属性
    who.dbase:setTemp("sorted_props", sorted_props);
end

-- 属性触发
function trigger(who, propId, key, value)
    Profiler.funcBegin("PropM.trigger");
    if type(propId) == "string" then
        propId = getPropId(propId);
    end

    -- 合并who的所有prop,key属性
    local prop = combine(who, propId, key);

    -- 应该是没有该属性
    if prop[3] <= 0 then
        return false;
    end

    local info = getRow(propId);

    -- 如果是非触发性的属性（一般是用来做负责计算），直接返回结果，不用走队列
    -- 天空之城战斗对象直接返回
    if who.type == OBJECT_TYPE_SKYFIGHTER or
        not info.trigger or #info.trigger <= 0 then
        local mod = queryRule(info.rule);
        if  mod and mod.trigger then
            return mod.trigger(who, prop, value);
        end

        return false;
    end

    -- 先加入触发队列末尾
    local triggerQueue = who.triggerQueue or {};

    table.insert(triggerQueue, { ["prop_id"] = propId, ["prop"] = prop, ["args"] = value, });
    who.triggerQueue = triggerQueue;

    local ret = true;
    local round = CombatM.getRound();

    repeat
        -- 如果当前正在触发中
        if round == who.trigger_prop_lock then
            break;
        end

        -- 如果队列空了
        triggerQueue = who.triggerQueue;
        if #triggerQueue <= 0 then
            break;
        end

        -- 标记为正在触发
        who.trigger_prop_lock = round;

        local data = triggerQueue[1];

        propId = data["prop_id"];
        prop   = data["prop"];
        value  = data["args"];

        local m = getRow(propId);
        local mod = queryRule(m.rule);
        if mod and mod.trigger then
            local status;
            status, ret = xpcall(function() return mod.trigger(who, prop, value); end, errorTraceback);
            if not status then
                -- 报错
                DungeonLogM.addLog(ret);
            end
        end

        -- pop队首
        table.remove(who.triggerQueue, 1);

        who.trigger_prop_lock = nil;

    until false;

    Profiler.funcEnd("PropM.trigger");
    return ret;
end

-- 记录buff
function recordBuff(target, prop, noRecover)

    -- 优先当做当做状态处理
    local condition = { ["prop"] = prop, ["end_round"] = prop[4], };

    -- 一个通用的debuff状态
    CombatStatusM.applyStatus(target, "buff", condition);

    EventMgr.fire(event.ADD_BUFF, { ["target"] = target, ["prop"] = prop, });
end

-- 记录debuff
function recordDebuff(target, prop, noRecover)

    -- 优先当做当做状态处理
    local condition = { ["prop"] = prop, ["end_round"] = prop[4], };

    -- 一个通用的debuff状态
    CombatStatusM.applyStatus(target, "debuff", condition);

    EventMgr.fire(event.ADD_DEBUFF, { ["target"] = target, ["prop"] = prop, });

    -- 如果是怪物，需要抛出一个事件
    if target.type == OBJECT_TYPE_MONSTER then
        EventMgr.fire(event.MONSTER_INFO_UPDATED, {["pos"] = target:getPos(), });
    end
end

-- 删除某目录下的某种属性
function deletePropById(target, path, propId, key)
    if type(propId) == "string" then
        propId = getPropId(propId);
    end

    local props = target.dbase:queryTemp("prop", path, {});
    if #props <= 0 then
        return;
    end

    local remainList = {};
    local cleanList = {};
    for index = 1, #props do
        local prop = props[index];
        if prop[1] == propId and
            (not key or tostring(prop[2]) == tostring(key)) then
            -- 需要清除的属性
            cleanList[index] = prop;
        else
            -- 否则，需要保留
            table.insert(remainList, prop);
        end
    end

    -- 记录下来
    target.dbase:setTemp("prop", path, remainList);

    -- 重整下属性
    arrangeProps(target);

    -- 通知清除某属性
    for _, prop in pairs(cleanList) do
        clearProp(target, prop);
    end
end

-- 删除某类属性
function deleteProp(target, parent, clear)
    -- 如果没有传值就默认是true
    clear = iif(clear == nil, true, clear);

    local props = target.dbase:queryTemp("prop", parent, {});

    local ret = {};
    local stay = {};
    for index = 1, #props do
        local prop = props[index];
        -- 不允许删除的
        if not clear and query(prop[1], "forbid_clear") == 1 then
            -- 没有删除掉的重新加进来
            table.insert(ret, prop);

            stay[index] = true;
        end
    end

    -- 删除永久属性
    deleteForever(target, parent);

    -- 重新设置上去
    target.dbase:setTemp("prop", parent, ret);

    -- 整理一次属性
    arrangeProps(target);

    -- 通知清除某属性
    for index, prop in pairs(props) do
        if not stay[index] and prop then
            clearProp(target, prop);
        end
    end
end

-- 删除每类属性中的一条
function deleteOneProp(target, parent, index)
    local props = target.dbase:queryTemp("prop", parent, {});

    if index < 1 or index > #props then
        return;
    end

    local prop = props[index];

    -- 先删除
    table.remove(props, index);
    target.dbase:setTemp("prop", parent, props);

    -- 整理一次属性
    arrangeProps(target);

    -- 通知清除某属性
    clearProp(target, prop);
end

-- 获取增益属性
function getBuff(user)
    local ret = CombatStatusM.fetchAllProps(user, COM_STATUS_BUFF);

    local buff = user.dbase:queryTemp("prop", "buff", {});
    ret = table.append(ret, buff);

    return table.deepcopy(ret);
end

-- 获取减益属性
function getDebuff(who)
    local debuff = CombatStatusM.fetchAllProps(who, COM_STATUS_DEBUFF);
    debuff = table.append(debuff, who.dbase:queryTemp("prop", "debuff", {}));
    debuff = table.append(debuff, who.dbase:queryTemp("prop", "magic_stone", {}));   -- 石柱的debuff

    return table.deepcopy(debuff);
end

-- 获取特殊属性
function getSpecialProp(who)
    -- 特殊属性
    local props = who.dbase:queryTemp("prop", "special", {});

    local shield = who.dbase:queryTemp("prop", "shield", {});
    -- 加上护盾
    if #shield > 0 then
        props = table.append(props, { shield[#shield], });
    end

    local ancient_memory = who.dbase:queryTemp("prop", "ancient_memory", {});
    -- 远古记忆
    if #ancient_memory > 0 then
        props = table.append(props, { ancient_memory[#ancient_memory] });
    end

    local m = who.dbase:query("forever_prop", {});
    -- 再加上神圣重生
    if m["holy_revive"] then
        props = table.append(props, who.dbase:queryTemp("prop", "holy_revive", {}));
    end

    -- 状态加成的属性
    props = table.append(props, CombatStatusM.fetchAllProps(who, COM_STATUS_SPECIAL));

    return table.deepcopy(props);
end

-- 检查一串属性中是否有指定的属性
function haveProp(props, propId, key)
    if type(propId) ~= "number" then
        propId = getPropId(propId);
    end

    for _, prop in pairs(props) do
        if tostring(prop[2]) == tostring(key) then
            if propId == -1 or tonumber(prop[1]) == propId then
                return true;
            end
        end
    end

    return false;
end

-- 获取属性图标
function getPropIcon(prop)
    local icon = query(prop[1], "icon");

    if not icon then
        return;
    end

    -- 如果配置的是整数
    if type(icon) == "number" then
        return icon;
    end

    return icon[prop[2]];
end

-- 合并多条附加属性
function combineMulti(param)
    local id = param[1][1];
    local key = param[1][2];
    local v = 0;
    for i = 1, #param do
        v = v + param[i][3];
    end

    return { id, key, v };
end

-- 刷新属性效果
function refreshEffect(who, type, notArrange)
    Profiler.funcBegin("PropM.refreshEffect");
    local props;

    if type == "equip" then
        -- 先删除
        who.dbase:deleteTemp("prop", "equip");

        -- 刷新装备属性
        props = EquipM.fetchAllProps(who);
        for _, prop in ipairs(props) do
            record(who, prop, "equip", true)
        end
    elseif type == "lictor" then
        -- 先删除
        who.dbase:deleteTemp("prop", "lictor");

        -- 刷新扈从属性
        props = LictorM.fetchAllProps(who);
        for _, prop in ipairs(props) do
            record(who, prop, "lictor", true)
        end
    elseif type == "skill" then
        -- 先删除
        who.dbase:deleteTemp("prop", "skill");

        local skills = {};

        -- 玩家
        if who.type == OBJECT_TYPE_USER then
            skills = table.append(who:getAllSkills(), who:getTalents());
        elseif who.type == OBJECT_TYPE_MONSTER then
            skills = who:getAllSkills();
        elseif who.type == OBJECT_TYPE_PET then
            skills = who:getAllSkills();
        elseif who.type == OBJECT_TYPE_BINE then
            skills = who:getAllSkills();
        elseif who.type == OBJECT_TYPE_SUMMON then
            skills = who:getAllSkills();
        elseif who.type == OBJECT_TYPE_BISHOP then
            skills = table.keys(who:getAllSkills());
        elseif who.type == OBJECT_TYPE_AVALON_CANNON then
            skills = who:getSkills();
        else
            -- 其他的
            skills = who:getAllSkills();
        end

        -- 根据最终技能等级计算属性
        for _, skillId in ipairs(skills) do
            SkillM.castPassive(who, FormulaM.invoke("CALC_FINAL_SKILL", who, skillId));
        end
    elseif type == "combat_status" then
        -- 状态属性
        who.dbase:deleteTemp("prop", "combat_status");

        for _, prop in ipairs(CombatStatusM.fetchAllProps(who)) do
            record(who, table.copy(prop), "combat_status", true);
        end
    elseif type == "food" then
        -- 先删除旧数据
        who.dbase:deleteTemp("prop", "food");

        -- 刷新喂食的效果
        props = PetM.fetchFeedProps(who);

        for _, prop in ipairs(props) do
            record(who, table.copy(prop), "food", true)
        end
    elseif type == "awake_carving" then
        -- 先删除旧数据
        who.dbase:deleteTemp("prop", "awake_carving");

        -- 刷新觉醒雕像的效果
        props = AwakeM.fetchAwakeCarvingProps(who);

        for _, prop in ipairs(props) do
            record(who, table.copy(prop), "awake_carving", true)
        end
    elseif type == "fate_chain" then
        -- 先删除旧数据
        who.dbase:deleteTemp("prop", "fate_chain");

        -- 刷新命运之链效果
        props = FateChainM.fetchFateProps(who);

        for _, prop in ipairs(props) do
            record(who, table.copy(prop), "fate_chain", true);
        end
    elseif type == "workshop" then
        -- 先删除旧数据
        who.dbase:deleteTemp("prop", "workshop");

        -- 刷新炼金熔炉的效果
        props = AlchemyWorkshopM.fetchProps(who);
        for _, prop in ipairs(props) do
            record(who, prop, "workshop", true);
        end
    elseif type == "relic_func" then
        -- 先删除旧属性
        who.dbase:deleteTemp("prop", "relic_func");

        -- 建筑功能属性
        props = SkyRelicFunctionM.getAllProps(who);
        for _, prop in ipairs(props) do
            record(who, prop, "relic_func", true);
        end
    elseif type == "pet" then
        if who.type ~= OBJECT_TYPE_USER then
            return;
        end

        -- 先删除旧属性
        who.dbase:deleteTemp("prop", "pet");

        -- 取得出战宠物的属性
        local pet = who:getActivePet();
        local ret = {};
        if pet then
            local petProps = pet.dbase:queryTemp("prop", {});
            for _, propList in pairs(petProps) do
                for _, prop in ipairs(propList) do
                    table.insert(ret, prop);
                end
            end
            who.dbase:setTemp("prop", "pet", ret);
        end
    elseif type == "summon" then
        if who.type ~= OBJECT_TYPE_USER then
            return;
        end

        -- 先删除旧属性
        who.dbase:deleteTemp("prop", "summon");

        -- 如果是在迷宫中，取得召唤物的属性
        if DungeonM.isInDungeon() then
            local summon_list = who.dbase:queryTemp("summon_list", {});
            local ret = {};
            for _, summon in ipairs(summon_list) do
                if not summon:isDead() and
                    (summon:query("enhance_user") == 1) then
                    local summonProps = summon.dbase:queryTemp("prop", {});
                    for _, propList in pairs(summonProps) do
                        for _, prop in ipairs(propList) do
                            table.insert(ret, prop);
                        end
                    end
                end
            end
            who.dbase:setTemp("prop", "summon", ret);
        end
    elseif type == "engineering_manual" then
        -- 先删除旧属性
        who.dbase:deleteTemp("prop", "engineering_manual");

        -- 刷新工程学手册的效果
        props = EngineeringManualM.fetchProps();
        for _, prop in ipairs(props) do
            record(who, prop, "engineering_manual", true);
        end
    elseif type == "summon_engineering_manual" then
        -- 先删除旧属性
        who.dbase:deleteTemp("prop", "summon_engineering_manual");

        -- 工程学手册给召唤兽的加成
        props = EngineeringManualM.fetchSummonProps(who);
        for _, prop in ipairs(props) do
            record(who, prop, "summon_engineering_manual", true);
        end
    elseif type == "summon_prop" then
        -- 先删除旧属性
        who.dbase:deleteTemp("prop", "summon_prop");

        -- 对召唤兽的加成
        props = SummonM.fetchSummonProps(who);
        for _, prop in ipairs(props) do
            record(who, prop, "summon_prop", true);
        end
    elseif type == "property_engineeing_manual" then
        -- 先删除旧属性
        who.dbase:deleteTemp("prop", "property_engineeing_manual");

        -- 对道具的加成
        props = EngineeringManualM.fetchPropertyAddProps();
        for _, prop in ipairs(props) do
            record(who, prop, "property_engineeing_manual", true);
        end
    else
        -- 其他的待添加
    end

    if not notArrange then
        -- 整理合并一次属性
        arrangeProps(who);

        -- 抛出事件
        EventMgr.fire(event.RERESH_PROP, {["who"] = who, ["type"] = type, });

        -- 若为出战宠物属性刷新，则玩家 pet 属性也要更新
        if who.type == OBJECT_TYPE_PET and PetM.isActivePet(who.classId) then
            refreshEffect(ME.user, "pet");
        end

        -- 若为辅助召唤物属性刷新，则玩家 summon 属性也要更新
        if who.type == OBJECT_TYPE_SUMMON and who:query("enhance_user") == 1 then
            -- 辅助召唤物，需要刷新玩家的 summon 属性
            refreshEffect(ME.user, "summon");
        end
    end


    Profiler.funcEnd("PropM.refreshEffect");
end

-- 幽灵船附加属性
refreshShip = function(ship)
    -- 技能
    refreshEffect(ship, "skill", true);

    -- 状态属性
    refreshEffect(ship, "combat_status", true);
end

-- 刷新召唤兽的附加属性
refreshSummon = function(summon)
    -- 刷新技能
    refreshEffect(summon, "skill", true);

    -- 装备（海怪船长之类的）
    refreshEffect(summon, "equip", true);

    -- 状态属性
    refreshEffect(summon, "combat_status", true);

    -- 刷新主动技能属性
    refreshEffect(summon, "summon_engineering_manual", true);

    refreshEffect(summon, "summon_prop", true);

    if summon:query("enhance_user") == 1 then
        -- 辅助召唤物，需要刷新玩家的 summon 属性
        refreshEffect(ME.user, "summon");
    end
end

-- 刷新魔藤的附加属性
refreshBine = function(bine)
    -- 刷新技能
    refreshEffect(bine, "skill", true);

    -- 状态属性
    refreshEffect(bine, "combat_status", true);
end

-- 刷新主教的附加属性
refreshBishop = function(bishop)
    -- 刷新技能
    refreshEffect(bishop, "skill", true);

    -- 状态属性
    refreshEffect(bishop, "combat_status", true);
end

-- 刷新阿瓦隆巨炮属性
refreshAvalonCannon = function(avalonCannon)
    -- 刷新技能
    refreshEffect(avalonCannon, "skill", true);

    -- 状态属性
    refreshEffect(avalonCannon, "combat_status", true);
end

-- 刷新怪物的附加属性
refreshMonster = function(monster)
    -- 装备属性
    refreshEffect(monster, "skill", true);

    -- 状态属性
    refreshEffect(monster, "combat_status", true);
end

-- 刷新宠物的附加属性
refreshPet = function(pet)
    -- 装备属性
    refreshEffect(pet, "equip", true);

    -- 喂食效果
    refreshEffect(pet, "food", true);

    -- 觉醒雕像效果
    refreshEffect(pet, "awake_carving", true);

    -- 刷新命运之链
    refreshEffect(pet, "fate_chain", true);

    -- 状态属性
    refreshEffect(pet, "combat_status", true);

    if PetM.isActivePet(pet.classId) or PetM.isLictorPet(pet.classId) then
        -- 出战/链接宠物，需要刷新玩家的 pet 属性
        refreshEffect(ME.user, "pet");

        -- 宠物属性对skill有影响，需要先arrangeProps一下
        arrangeProps(ME.user);

        -- 技能属性，技能对属性有影响，需要先刷
        refreshEffect(ME.user, "skill");
    end
end

-- 刷新玩家的附加属性
refreshUser = function(user)
    -- 出战宠物，宠物对skill有影响
    refreshEffect(user, "pet", true);

    -- 宠物属性对skill有影响，需要先arrangeProps一下
    arrangeProps(user);

    -- 技能属性，技能对属性有影响，需要先刷
    refreshEffect(user, "skill", true);

    -- 状态属性
    refreshEffect(user, "combat_status", true);

    -- 扈从属性
    refreshEffect(user, "lictor", true);

    -- 工程学手册
    refreshEffect(user, "engineering_manual", true);

    -- 对道具的额外加成
    refreshEffect(user, "property_engineeing_manual", true);

    -- 在刷新装备前先重整属性，因为装备中神器属性需要
    arrangeProps(user);

    -- 考古家BUG，需要临时再刷一次equip
    refreshEffect(user, "equip", true);

    -- 炼金
    refreshEffect(user, "workshop", true);

    -- 遗迹建筑功能
    refreshEffect(user, "relic_func", true);

    -- 召唤物
    refreshEffect(user, "summon", true);
end

function getShieldHit()
    -- 抵挡伤害
    -- 护盾状态
    local shieldStatus = CombatStatusM.getStatusByRule("shield");
    local total = 0;

    for index = 1, #shieldStatus do
        local status = shieldStatus[index];
        local condition = CombatStatusM.queryCondition(ME.user, status) or {};
        local shield = condition["shield"] or 0;

        total = total + shield;
    end

    return total;
end

function getSpecialDesc(prop, classId)
    local row = getRow(prop[1]);
    if row == nil then
        return "";
    end

    local rule = queryRule(row.rule);

    if not rule then
        cclog("没有相应的子模块: " .. row.id .. ", rule: " .. row.rule);
        return "";
    end

    -- 子规则处理
    if rule.desc then
        return rule.desc(prop, classId);
    end

    return "";
end

